#include <inc/mmu.h>
#include <inc/x86.h>
#include <inc/assert.h>
#include <inc/log.h>

#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/console.h>
#include <kern/monitor.h>
#include <kern/env.h>
#include <kern/syscall.h>
#include <kern/sched.h>
#include <kern/kclock.h>
#include <kern/picirq.h>

static struct Taskstate ts;
static uint32_t syscall_count;

/* Interrupt descriptor table.  (Must be built at run time because
 * shifted function addresses can't be represented in relocation records.)
 */
struct Gatedesc idt[256] = { { 0 } };
struct Pseudodesc idt_pd = {
	sizeof(idt) - 1, (uint32_t) idt
};


static const char *trapname(int trapno)
{
	static const char * const excnames[] = {
		"Divide error",
		"Debug",
		"Non-Maskable Interrupt",
		"Breakpoint",
		"Overflow",
		"BOUND Range Exceeded",
		"Invalid Opcode",
		"Device Not Available",
		"Double Fault",
		"Coprocessor Segment Overrun",
		"Invalid TSS",
		"Segment Not Present",
		"Stack Fault",
		"General Protection",
		"Page Fault",
		"(unknown trap)",
		"x87 FPU Floating-Point Error",
		"Alignment Check",
		"Machine-Check",
		"SIMD Floating-Point Exception"
	};

	if (trapno < sizeof(excnames)/sizeof(excnames[0]))
		return excnames[trapno];
	if (trapno == T_SYSCALL)
		return "System call";
	if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16)
		return "Hardware Interrupt";
	return "(unknown trap)";
}

/* For challenge 1 */
void H_SYSCALL();
extern uint32_t idt_handlers[], irq_handlers[];

void
idt_init(void)
{
	extern struct Segdesc gdt[];
	int i;

	/*
	  About the arguments: for SETGATE I choose to use interrupt
	  gates for all exceptions and interrupts since the kernel is
	  designed to run without concerning about interrupts
	*/

	// LAB 3: Your code here.
	// T_DIVIDE ~ T_SIMDERR
	for (i = 0; i <= T_SIMDERR; ++i) {
		// User could only access T_BRKPT, T_OFLOW, T_BOUND
		if (i == T_BRKPT || i == T_OFLOW || i == T_BOUND) {
			SETGATE(idt[i], 0, GD_KT, idt_handlers[i], 3);
		} else {
			SETGATE(idt[i], 0, GD_KT, idt_handlers[i], 0);
		}
	}
	// T_SYSCALL -- could be access by user
	SETGATE(idt[T_SYSCALL], 0, GD_KT, H_SYSCALL, 3);
	// IRQ 32~47 -- kernel only
	for (i = 32; i <= 47; ++i) {
		SETGATE(idt[i], 0, GD_KT, irq_handlers[i - 32], 0);
	}

	// Setup a TSS so that we get the right stack
	// when we trap to the kernel.
	ts.ts_esp0 = KSTACKTOP;
	ts.ts_ss0 = GD_KD;

	// Initialize the TSS field of the gdt.
	gdt[GD_TSS >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
					sizeof(struct Taskstate), 0);
	gdt[GD_TSS >> 3].sd_s = 0;

	// Load the TSS
	ltr(GD_TSS);

	// Load the IDT
	asm volatile("lidt idt_pd");
}

void
print_trapframe(struct Trapframe *tf)
{
	cprintf("TRAP frame at %p\n", tf);
	print_regs(&tf->tf_regs);
	cprintf("  es   0x----%04x\n", tf->tf_es);
	cprintf("  ds   0x----%04x\n", tf->tf_ds);
	cprintf("  trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno));
	cprintf("  err  0x%08x\n", tf->tf_err);
	cprintf("  eip  0x%08x\n", tf->tf_eip);
	cprintf("  cs   0x----%04x\n", tf->tf_cs);
	cprintf("  flag 0x%08x\n", tf->tf_eflags);
	cprintf("  esp  0x%08x\n", tf->tf_esp);
	cprintf("  ss   0x----%04x\n", tf->tf_ss);
}

void
print_regs(struct PushRegs *regs)
{
	cprintf("  edi  0x%08x\n", regs->reg_edi);
	cprintf("  esi  0x%08x\n", regs->reg_esi);
	cprintf("  ebp  0x%08x\n", regs->reg_ebp);
	cprintf("  oesp 0x%08x\n", regs->reg_oesp);
	cprintf("  ebx  0x%08x\n", regs->reg_ebx);
	cprintf("  edx  0x%08x\n", regs->reg_edx);
	cprintf("  ecx  0x%08x\n", regs->reg_ecx);
	cprintf("  eax  0x%08x\n", regs->reg_eax);
}

static void
trap_dispatch(struct Trapframe *tf)
{
	// Handle processor exceptions.
	// LAB 3: Your code here.
	switch (tf->tf_trapno) {
	case T_PGFLT:
		page_fault_handler(tf);
		return;
	case T_BRKPT:
		cprintf("Syscall Count: %d\n", syscall_count);
		monitor(tf);
		return;
	case T_SYSCALL:
		syscall_handler(tf);
		return;
	case IRQ_TIMER + IRQ_OFFSET:
		sched_yield();
		return;
	}

	if (tf->tf_trapno > IRQ_OFFSET && tf->tf_trapno <= 47) {
		cprintf("IRQ%d, ignore\n", tf->tf_trapno - IRQ_OFFSET);
		return;
	}
	// Handle clock and serial interrupts.
	// LAB 4: Your code here.

	// Handle keyboard interrupts.
	// LAB 5: Your code here.

	// Unexpected trap: The user process or the kernel has a bug.
	print_trapframe(tf);
	if (tf->tf_cs == GD_KT)
		panic("unhandled trap in kernel");
	else {
		env_destroy(curenv);
		return;
	}
}

void
trap(struct Trapframe *tf)
{
	if ((tf->tf_cs & 3) == 3) {
		// Trapped from user mode.
		// Copy trap frame (which is currently on the stack)
		// into 'curenv->env_tf', so that running the environment
		// will restart at the trap point.
		assert(curenv);
		curenv->env_tf = *tf;
		// The trapframe on the stack should be ignored from here on.
		tf = &curenv->env_tf;
	}

	// Dispatch based on what type of trap occurred
	trap_dispatch(tf);

	// If we made it to this point, then no other environment was
	// scheduled, so we should return to the current environment
	// if doing so makes sense.
	if (curenv && curenv->env_status == ENV_RUNNABLE)
		env_run(curenv);
	else
		sched_yield();
}

#define PUSHUTF(value, tf) do {\
	uint32_t tmp = (value);\
	*(uint32_t *)((tf).tf_esp -= 4) = tmp;\
	} while (0)

void
page_fault_handler(struct Trapframe *tf)
{
	uint32_t fault_va;


	// Read processor's CR2 register to find the faulting address
	fault_va = rcr2();

	// Handle kernel-mode page faults.

	// LAB 3: Your code here.
	if (!(tf->tf_cs & 3))
		panic("page_fault_handler: kernel-mode page fault %x %x\n", fault_va, tf->tf_err);

	// We've already handled kernel-mode exceptions, so if we get here,
	// the page fault happened in user mode.

	// Call the environment's page fault upcall, if one exists.  Set up a
	// page fault stack frame on the user exception stack (below
	// UXSTACKTOP), then branch to curenv->env_pgfault_upcall.
	//
	// The page fault upcall might cause another page fault, in which case
	// we branch to the page fault upcall recursively, pushing another
	// page fault stack frame on top of the user exception stack.
	//
	// The trap handler needs one word of scratch space at the top of the
	// trap-time stack in order to return.  In the non-recursive case, we
	// don't have to worry about this because the top of the regular user
	// stack is free.  In the recursive case, this means we have to leave
	// an extra word between the current top of the exception stack and
	// the new stack frame because the exception stack _is_ the trap-time
	// stack.
	//
	// If there's no page fault upcall, the environment didn't allocate a
	// page for its exception stack, or the exception stack overflows,
	// then destroy the environment that caused the fault.
	//
	// Hints:
	//   user_mem_assert() and env_run() are useful here.
	//   To change what the user environment runs, modify 'curenv->env_tf'
	//   (the 'tf' variable points at 'curenv->env_tf').

	// LAB 4: Your code here.
	if (curenv->env_pgfault_upcall) {
		// Save trap time esp
		uint32_t traptime_esp = curenv->env_tf.tf_esp;
		// See where we are
		if (curenv->env_tf.tf_esp >= UXSTACKTOP - PGSIZE
		    && curenv->env_tf.tf_esp < UXSTACKTOP) {
			// already in exception stack, check if there's enough room on stack
			user_mem_assert(curenv, (void *)(curenv->env_tf.tf_esp - 4 - sizeof(struct UTrapframe)), 4 + sizeof(struct UTrapframe), PTE_U | PTE_W);
			// push a 32bit word for return value
			curenv->env_tf.tf_esp -= 4;
		} else {
			// Switch to UXSTACKTOP
			curenv->env_tf.tf_esp = UXSTACKTOP;
			// already in exception stack, check if there's enough room on stack
			user_mem_assert(curenv, (void *)(curenv->env_tf.tf_esp - sizeof(struct UTrapframe)), sizeof(struct UTrapframe), PTE_U | PTE_W);
		}

		// Form UTrapframe
		PUSHUTF(traptime_esp, curenv->env_tf); // attention this saves the traptime esp so we can go back
		PUSHUTF(curenv->env_tf.tf_eflags, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_eip, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_eax, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_ecx, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_edx, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_ebx, curenv->env_tf);
		PUSHUTF(traptime_esp, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_ebp, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_esi, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_regs.reg_edi, curenv->env_tf);
		PUSHUTF(curenv->env_tf.tf_err, curenv->env_tf);
		PUSHUTF(fault_va, curenv->env_tf);

		// Run upcall
		curenv->env_tf.tf_eip = (uint32_t)curenv->env_pgfault_upcall;
		env_run(curenv);
	}

	// Destroy the environment that caused the fault.
	cprintf("[%08x] user fault va %08x ip %08x\n",
		curenv->env_id, fault_va, tf->tf_eip);
	print_trapframe(tf);
	env_destroy(curenv);
}

void
syscall_handler(struct Trapframe *tf)
{
	++syscall_count;
	struct PushRegs *reg = &(tf->tf_regs);
	int res = syscall(reg->reg_eax,
			  reg->reg_edx, reg->reg_ecx, reg->reg_ebx,
			  reg->reg_edi, reg->reg_esi);
	/*
	if (res < 0)
		panic("syscall_handler: %e\n", res);
	*/
	reg->reg_eax = res;
}
